package com.zhuhjay.service.system.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.zhuhjay.common.base.exception.BasicBusinessException;
import com.zhuhjay.common.base.util.JacksonUtils;
import com.zhuhjay.common.redis.util.RedisUtils;
import com.zhuhjay.service.system.mapper.SystemMenuMapper;
import com.zhuhjay.service.system.model.dto.MenuDto;
import com.zhuhjay.service.system.model.entity.SystemMenu;
import com.zhuhjay.service.system.model.entity.SystemRoleMenu;
import com.zhuhjay.service.system.model.entity.SystemUserRole;
import com.zhuhjay.service.system.model.vo.MenuInfoVo;
import com.zhuhjay.service.system.model.vo.PermissionVo;
import com.zhuhjay.service.system.service.SystemMenuService;
import com.zhuhjay.service.system.service.SystemRoleMenuService;
import com.zhuhjay.service.system.service.SystemUserRoleService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static com.zhuhjay.common.base.constant.ApplicationConstant.SUPER_ADMIN_ID;
import static com.zhuhjay.common.base.constant.CacheConstant.ROLE_MENUS_PREFIX;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author Zhuhjay
 * @since 2023-02-13
 */
@Service
public class SystemMenuServiceImpl extends ServiceImpl<SystemMenuMapper, SystemMenu> implements SystemMenuService {

    @Resource
    private SystemRoleMenuService roleMenuService;

    @Resource
    private SystemUserRoleService userRoleService;

    @Override
    public List<String> getPermissionsByUserId(Integer id) {
        // 根据用户id获取角色id
        SystemUserRole userRole = userRoleService.lambdaQuery()
                .eq(SystemUserRole::getUserId, id).one();
        return getMenusByRoleId(userRole.getRoleId()).stream()
                .map(SystemMenu::getMenuKey).collect(Collectors.toList());
    }

    @Override
    public List<String> getPermissionsByRoleId(Integer id) {
        return getMenusByRoleId(id).stream()
                .map(SystemMenu::getMenuKey).collect(Collectors.toList());
    }

    @Override
    public boolean checkPermission(Integer id, String permission) {
        // 根据用户id获取角色id
        SystemUserRole userRole = userRoleService.lambdaQuery()
                .eq(SystemUserRole::getUserId, id).one();
        // 获取角色的权限列表(只需要获取叶子菜单的权限)
        List<Integer> menuIds = roleMenuService.getMenuIdsByRoleId(userRole.getRoleId());
        // 判断是否有权限
        return this.lambdaQuery().in(SystemMenu::getId, menuIds)
                .eq(SystemMenu::getMenuKey, permission).count() > 0;
    }

    @Override
    public List<MenuInfoVo> getMenusTreeList() {
        /* 获取所有菜单
            系统中的菜单有:
                - 一级菜单
                  - 二级菜单
                    - 二级菜单的子权限
        */
        List<SystemMenu> menus = this.lambdaQuery()
                // 先按照父id升序，再按照sort升序
                .orderByAsc(List.of(SystemMenu::getParentId, SystemMenu::getSort)).list();

        List<MenuInfoVo> result = new ArrayList<>();

        // 使用队列来进行层级遍历, 先将一级菜单放入队列
        Queue<MenuInfoVo> queue = new LinkedList<>();
        menus.stream()
                .filter(menu -> menu.getParentId() == -1)
                .map(menu -> {
                    MenuInfoVo menuInfoVo = MenuInfoVo.of(menu);
                    // 顺便将一级菜单进行记录
                    result.add(menuInfoVo);
                    return menuInfoVo;
                }).forEach(queue::add);

        while (!queue.isEmpty()) {
            MenuInfoVo menu = queue.poll();
            // 获取当前菜单的子菜单
            List<MenuInfoVo> children = menus.stream()
                    .filter(m -> m.getParentId().equals(menu.getId()))
                    .map(MenuInfoVo::of).collect(Collectors.toList());
            // 如果为叶子节点, 那么不返回children
            menu.setChildren(children.size() > 0 ? children : null);
            // 将子菜单加入队列
            queue.addAll(children);
        }
        return result;
    }

    @Override
    public List<PermissionVo> getPermissionsVoByUserId(Integer id) {
        // 如果是超级管理员, 那么直接返回所有权限(防止超级管理员修改自己的权限)
        if (id.equals(SUPER_ADMIN_ID)) {
            return this.lambdaQuery().orderByAsc(SystemMenu::getId).list().stream().map(menu -> {
                PermissionVo permissionVo = new PermissionVo();
                permissionVo.setLabel(menu.getMenuName());
                permissionVo.setValue(menu.getMenuKey());
                return permissionVo;
            }).collect(Collectors.toList());
        }
        // 根据用户id获取角色id
        SystemUserRole userRole = userRoleService.lambdaQuery()
                .eq(SystemUserRole::getUserId, id).one();
        return getMenusByRoleId(userRole.getRoleId()).stream().map(menu -> {
            PermissionVo permissionVo = new PermissionVo();
            permissionVo.setLabel(menu.getMenuName());
            permissionVo.setValue(menu.getMenuKey());
            return permissionVo;
        }).collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(MenuDto dto) {
        // 判断key是否存在
        this.lambdaQuery().eq(SystemMenu::getMenuKey, dto.getMenuKey())
                .oneOpt().ifPresent(r -> {
                    throw new BasicBusinessException("菜单key已存在");
                });
        SystemMenu menu = dto.bean2Entity();

        if (menu.getParentId() != -1) {
            // 判断parentId是否存在
            this.lambdaQuery().eq(SystemMenu::getId, menu.getParentId())
                    .oneOpt().orElseThrow(() -> new BasicBusinessException("父级菜单不存在"));
        }

        // 默认菜单序列都是有序的
        List<SystemMenu> menus = this.lambdaQuery()
                .eq(SystemMenu::getParentId, menu.getParentId()).orderByAsc(SystemMenu::getSort).list();
        // 如果新增的sort大于数据库中的最大sort, 则直接添加到最后
        if (menu.getSort() > menus.size()) {
            menu.setSort(menus.size() + 1);
        } else {
            // 如果新增的sort小于数据库中的最大sort, 则需要将新增sort后面的所有sort都加1
            menus.stream().filter(m -> m.getSort() >= menu.getSort())
                    .forEach(m -> m.setSort(m.getSort() + 1));
            this.updateBatchById(menus);
        }
        this.save(menu);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(Integer id, MenuDto dto) {
        SystemMenu menu = this.getById(id);
        if (menu == null) {
            throw new BasicBusinessException("菜单不存在");
        }
        boolean isUpdate = false;
        // 如果菜单key发生了变化, 则需要判断是否存在该key
        if (!menu.getMenuKey().equals(dto.getMenuKey())) {
            // 判断key是否存在
            this.lambdaQuery().eq(SystemMenu::getMenuKey, dto.getMenuKey())
                    .oneOpt().ifPresent(m -> {
                        throw new BasicBusinessException("菜单key已存在");
                    });
            menu.setMenuKey(dto.getMenuKey());
            isUpdate = true;
        }

        boolean parentChange = false;
        // 如果父级菜单发生了变化, 则需要判断是否存在该父级菜单
        if (!menu.getParentId().equals(dto.getParentId())) {
            // 如果父级菜单为-1, 则不需要判断
            if (dto.getParentId() != -1) {
                // 判断parentId是否存在
                this.lambdaQuery().eq(SystemMenu::getId, dto.getParentId())
                        .oneOpt().orElseThrow(() -> new BasicBusinessException("父级菜单不存在"));
            }
            // 将原来的菜单排序进行调整
            AtomicInteger sort = new AtomicInteger(1);
            List<SystemMenu> updateMenus = this.lambdaQuery()
                    // 查询出所有兄弟菜单
                    .eq(SystemMenu::getParentId, menu.getParentId()).orderByAsc(SystemMenu::getSort).list()
                    // 过滤掉被移动走的菜单
                    .stream().filter(m -> !m.getId().equals(menu.getId()))
                    // 对剩余的菜单进行重新记录序列(本来就有序, 只是需要重新记录序列)
                    .peek(m -> m.setSort(sort.getAndIncrement())).collect(Collectors.toList());
            // 进行更新
            this.updateBatchById(updateMenus);
            parentChange = true;
            menu.setParentId(dto.getParentId());
            isUpdate = true;
        }

        // 如果菜单名称发生了变化, 那么需要进行更新
        if (!menu.getMenuName().equals(dto.getMenuName())) {
            menu.setMenuName(dto.getMenuName());
            isUpdate = true;
        }

        // 如果排序发生了变化, 则需要进行排序
        List<SystemMenu> menus = this.lambdaQuery()
                .eq(SystemMenu::getParentId, menu.getParentId()).orderByAsc(SystemMenu::getSort).list();
        if (!menu.getSort().equals(dto.getSort()) || parentChange) {
            // 是否父级菜单发生了变化
            if (!parentChange) {
                // 从原来的位置移除
                menus.remove(menu.getSort() - 1);
            }
            // 对排序字段进行重排
            reorderSort(dto, menu, menus);
            // 进行其他菜单的排序更新
            this.updateBatchById(menus);
            isUpdate = true;
        }
        if (isUpdate) {
            this.updateById(menu);
        }
    }

    private static void reorderSort(MenuDto dto, SystemMenu menu, List<SystemMenu> menus) {
        if (dto.getSort() > menus.size()) {
            // 超出了原来的排序, 则放置到最后
            menus.add(menu);
        } else {
            // 将菜单放置到新的位置
            menus.add(dto.getSort() - 1, menu);
        }
        // 重新记录序列
        for (int i = 0; i < menus.size(); i++) {
            menus.get(i).setSort(i + 1);
        }
        // 移除自己
        menus.remove(menu);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Integer id) {
        // 查看数据库中是否存在该数据
        SystemMenu menu = this.getById(id);
        if (menu == null) {
            throw new BasicBusinessException("该菜单不存在");
        }
        // 该菜单有子菜单, 不能删除
        if (this.lambdaQuery().eq(SystemMenu::getParentId, id).count().intValue() > 0) {
            throw new BasicBusinessException("该菜单下存在子菜单, 不能删除");
        }
        // 如果该菜单被使用, 则不能删除
        if (roleMenuService.lambdaQuery().eq(SystemRoleMenu::getMenuId, id).count().intValue() > 0) {
            throw new BasicBusinessException("该菜单已被关联使用, 不能删除");
        }
        // 删除菜单
        this.removeById(id);
        // 将排序大于该菜单的菜单进行修改排序 -> sort - 1
        this.lambdaUpdate()
                .eq(SystemMenu::getParentId, menu.getParentId())
                .gt(SystemMenu::getSort, menu.getSort())
                .setSql("sort = sort - 1").update();
    }

    /**
     * 根据角色id获取该角色的完整权限菜单
     * 该方法耗时比较久, 使用缓存进行优化
     */
    private List<SystemMenu> getMenusByRoleId(Integer id) {
        // 从缓存中获取(获取到的只是角色的所有菜单id)
        String roleMenuIdsStr = RedisUtils.getCache(ROLE_MENUS_PREFIX + id);
        if (StrUtil.isNotBlank(roleMenuIdsStr)) {
            List<Integer> value = JacksonUtils.readValue(roleMenuIdsStr, new TypeReference<List<Integer>>() {
            });
            return this.lambdaQuery().in(SystemMenu::getId, value).orderByAsc(SystemMenu::getId).list();
        }
        // 根据角色id获取菜单id(都是叶子结点)
        List<Integer> menuIds = roleMenuService.getMenuIdsByRoleId(id);
        List<SystemMenu> result = new ArrayList<>();
        // 存储该角色拥有的所有权限菜单的id
        Set<Integer> roleMenuIds = new HashSet<>(menuIds);
        Queue<Integer> waitGetMenuIds = new LinkedList<>(menuIds);
        while (!waitGetMenuIds.isEmpty()) {
            Integer menuId = waitGetMenuIds.poll();
            SystemMenu menu = this.getById(menuId);
            result.add(menu);
            // 如果不是根节点, 并且该结点不存在等待查询队列中, 则需要将父节点加入到队列中
            if (menu.getParentId() != -1 && !waitGetMenuIds.contains(menu.getParentId())) {
                waitGetMenuIds.add(menu.getParentId());
                roleMenuIds.add(menu.getParentId());
            }
        }
        // 对菜单进行排序(根据id顺序)
        List<SystemMenu> menus = result.stream().sorted(Comparator.comparing(SystemMenu::getId)).collect(Collectors.toList());
        // 存入缓存(只存储该角色的所有菜单id)
        RedisUtils.setCache(ROLE_MENUS_PREFIX + id, JacksonUtils.writeValueAsString(roleMenuIds.stream().sorted()));
        return menus;
    }
}
